package zjut.raymond.algorithm;

import com.alibaba.fastjson.JSON;
import zjut.raymond.pojo.ReferencePoint;

import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Raymond
 * @Description 指纹匹配算法
 * @date 2021/6/17-10:36
 */
public class WifiAlgorithm {
    private WifiAlgorithm() {
    }

    /**
     * 加权贝叶斯概率算法
     * @param wifiFingerPrintList
     * @param fingerprint
     * @param k
     * @return
     */
    public static List<CalculateResult> weightedBayesianAlgorithm(List<Map<String, Object>> wifiFingerPrintList, Map<String, Object> fingerprint, int k) {
        double curPro = 0.0;
        double curDis = 0.0;
        List<CalculateResult> calculateResults = new ArrayList<>();
      //  String mylocation;
        // for (WifiFingerPrint wifiFingerPrint : wifiFingerPrints) {
        //     curPro = calculateWeightProbability(wifiFingerPrint.getAverageRssi(), observeValue, wifiFingerPrint.getStandardDeviation());
        //     curDis = calculateEuclideanDistance(wifiFingerPrint.getAverageRssi(), observeValue);
        //     locDistanceList.add(0, new LocDistance(curDis, curPro, wifiFingerPrint.getReferencePoint()));
        // }

        for (Map<String, Object> wifiFingerPrint : wifiFingerPrintList) {
            String ap_rssi_avg_dev = (String) wifiFingerPrint.get("ap_rssi_avg_dev");
            List<WifiFeatures> wifiFeaturesList = JSON.parseArray(ap_rssi_avg_dev, WifiFeatures.class);
            Map<String, WifiFeatures> mapList = listToMap(wifiFeaturesList);
            curPro = calculateWeightProbability(mapList, fingerprint);
            curDis = calculateEuclideanDistance(mapList, fingerprint);
            calculateResults.add(0, new CalculateResult((Integer) wifiFingerPrint.get("reference_point_id"), curPro, curDis));
        }
        calculateResults.sort((o1, o2) -> (Double.compare(o2.getProbability(), o1.getProbability())));
        return calculateResults.subList(0, k);
        // mylocation = calculateWeightedAverageKDistanceLocations(locDistanceList, k);
        // LocationResult locationResult = new LocationResult(mylocation, locDistanceList);
        // wifiAlgorithmCallback.onLocationChange(locationResult);

    }

    /**
     * 朴素贝叶斯算法
     * @param wifiFingerPrintList
     * @param fingerprint
     * @param k
     * @return
     */
    public static List<CalculateResult> bayesianAlgorithm(List<Map<String, Object>> wifiFingerPrintList, Map<String, Object> fingerprint, int k) {
        double curPro = 0.0;
        double curDis = 0.0;
        List<CalculateResult> calculateResults = new ArrayList<>();
     //   String mylocation;
        // for (WifiFingerPrint wifiFingerPrint : wifiFingerPrints) {
        //     curPro = calculateWeightProbability(wifiFingerPrint.getAverageRssi(), observeValue, wifiFingerPrint.getStandardDeviation());
        //     curDis = calculateEuclideanDistance(wifiFingerPrint.getAverageRssi(), observeValue);
        //     locDistanceList.add(0, new LocDistance(curDis, curPro, wifiFingerPrint.getReferencePoint()));
        // }

        for (Map<String, Object> wifiFingerPrint : wifiFingerPrintList) {
            String ap_rssi_avg_dev = (String) wifiFingerPrint.get("ap_rssi_avg_dev");
            List<WifiFeatures> wifiFeaturesList = JSON.parseArray(ap_rssi_avg_dev, WifiFeatures.class);
            Map<String, WifiFeatures> mapList = listToMap(wifiFeaturesList);
            curPro = calculateProbability(mapList, fingerprint);
            curDis = calculateEuclideanDistance(mapList, fingerprint);
            calculateResults.add(0, new CalculateResult((Integer) wifiFingerPrint.get("reference_point_id"), curPro, curDis));
        }
        calculateResults.sort((o1, o2) -> (Double.compare(o2.getProbability(), o1.getProbability())));
        return calculateResults.subList(0, k);
    }

    /**
     * WKNN算法
     * @param wifiFingerPrintList
     * @param fingerprint
     * @param k
     * @return
     */

    public static List<CalculateResult> weightKNNAlgorithm(List<Map<String, Object>> wifiFingerPrintList, Map<String, Object> fingerprint, int k) {
      //  double curPro = 0.0;
        double curDis = 0.0;
        List<CalculateResult> calculateResults = new ArrayList<>();
        String mylocation;
        // for (WifiFingerPrint wifiFingerPrint : wifiFingerPrints) {
        //     curPro = calculateWeightProbability(wifiFingerPrint.getAverageRssi(), observeValue, wifiFingerPrint.getStandardDeviation());
        //     curDis = calculateEuclideanDistance(wifiFingerPrint.getAverageRssi(), observeValue);
        //     locDistanceList.add(0, new LocDistance(curDis, curPro, wifiFingerPrint.getReferencePoint()));
        // }

        for (Map<String, Object> wifiFingerPrint : wifiFingerPrintList) {
            String ap_rssi_avg_dev = (String) wifiFingerPrint.get("ap_rssi_avg_dev");
            List<WifiFeatures> wifiFeaturesList = JSON.parseArray(ap_rssi_avg_dev, WifiFeatures.class);
            Map<String, WifiFeatures> mapList = listToMap(wifiFeaturesList);
        //    curPro = calculateProbability(mapList, fingerprint);
            curDis = calculateEuclideanDistance(mapList, fingerprint);
            calculateResults.add(0, new CalculateResult((Integer) wifiFingerPrint.get("reference_point_id"), curDis));
        }
        calculateResults.sort(Comparator.comparingDouble(CalculateResult::getEuclideanDistance));
        return calculateResults.subList(0, k);
    }


    public static Map<String, WifiFeatures> listToMap(List<WifiFeatures> wifiFeaturesList) {
        Map<String, WifiFeatures> map = new HashMap<>();
        for (WifiFeatures wifiFeatures : wifiFeaturesList) {
            map.put(wifiFeatures.getApMac(), wifiFeatures);
        }
        return map;
    }

    private static double getSingleProbability(double aDouble, double mean, double standardDeviation) {
        double temp;
        temp = aDouble - mean;
        temp = -Math.pow(temp, 2);
        temp /= 2.0 * Math.pow(standardDeviation, 2);
        temp = Math.exp(temp) / standardDeviation;
        return temp;
    }

    /**
     * 计算贝叶斯概率
     */
    private static double calculateProbability(Map<String, WifiFeatures> offlineFingerPrints, Map<String, Object> onlineFingerprint) {
        double finalResult = 1.0d;
        double temp;

        for (String key : onlineFingerprint.keySet()) {
            WifiFeatures wifiFeatures = offlineFingerPrints.getOrDefault(key, new WifiFeatures(key, -110.0, 5.0));
            Integer intKey = (Integer) onlineFingerprint.get(key);
            temp = getSingleProbability(intKey.doubleValue() , wifiFeatures.getMean(), wifiFeatures.getStandardDeviation());

            if (finalResult * temp != 0.0) {
                finalResult = finalResult * temp;
            }
        }
        return Double.isNaN(finalResult) ? 0.0 : finalResult;
    }

    /**
     * 计算加权贝叶斯概率：标准差越小，ap稳定性越大，所占权重越大
     */
    private static double calculateWeightProbability(Map<String, WifiFeatures> offlineFingerPrints, Map<String, Object> onlineFingerprint) {
        double finalResult = 1.0d;
        double weight = 0.0d, sumWeight = 0.0d;
        double temp;
        for (String key : onlineFingerprint.keySet()) {
            WifiFeatures wifiFeatures = offlineFingerPrints.getOrDefault(key, new WifiFeatures(key, -110.0d, 5.0d));
            weight = 1.0 / wifiFeatures.getStandardDeviation();
            sumWeight += weight;
            Integer o = (Integer) onlineFingerprint.get(key);
            temp = getSingleProbability(o.doubleValue(), wifiFeatures.getMean(), wifiFeatures.getStandardDeviation());

            if (finalResult * temp != 0.0) {
                finalResult = finalResult * temp * weight;
            }
        }
        return Double.isNaN(finalResult) ? 0.0 : finalResult / sumWeight;
    }

    /**
     * 计算欧几里得距离
     *
     * @param offlineFingerPrints
     * @param onlineFingerprint
     * @return
     */

    private static Double calculateEuclideanDistance(Map<String, WifiFeatures> offlineFingerPrints, Map<String, Object> onlineFingerprint) {
        double finalResult = 0.0;
        double temp;
        //  DecimalFormat df = new DecimalFormat("0.00");
        for (String key : onlineFingerprint.keySet()) {
            WifiFeatures wifiFeatures = offlineFingerPrints.getOrDefault(key, new WifiFeatures(key, -110.0d, 5.0d));
            Integer o = (Integer) onlineFingerprint.get(key);
            temp = Math.pow((o.doubleValue() - wifiFeatures.getMean()), 2);
            finalResult += temp;
        }

        return Math.sqrt(finalResult);
    }

    public static Map<String, Double> getLocation(List<CalculateResult> calculateResults, List<ReferencePoint> referencePointList) {
        double locationWeight = 0.0d;
        double sumWeight = 0.0d;
        double sumX = 0.0d;
        double sumY = 0.0d;
        double resX, resY;
        double x, y;

        for (CalculateResult calculateResult : calculateResults) {
            if (calculateResult.getEuclideanDistance() != 0.0d) {
                locationWeight = 1 / calculateResult.getEuclideanDistance();
            } else {
                locationWeight = 100;
            }
            x = ReferencePoint.getXById(referencePointList, calculateResult.getReferenceId());
            y = ReferencePoint.getYById(referencePointList, calculateResult.getReferenceId());
            sumWeight += locationWeight;
            sumX += locationWeight * x;
            sumY += locationWeight * y;
        }
        resX = sumX / sumWeight;
        resY = sumY / sumWeight;
        HashMap<String, Double> res = new HashMap<>();
        res.put("x", resX);
        res.put("y", resY);
        return res;
    }
}
